/* 
 * File:   Construct.cpp
 * Author: brady
 * 
 * Created on April 11, 2011, 10:42 AM
 */

#include "Construct.h"

Construct::Construct() {
}
Construct::Construct(Shape* leftShape, Shape* rightShape, int operation)
{
    this->LeftShape = leftShape;
    this->RightShape = rightShape;
    this->Operation = operation;
}
Construct::~Construct() {
    this->Operation = Construct::UNION;
}

bool Construct::TestIntersect(Ray& ray, double tmin, double tmax, HitStruct& hitStruct)
{
    //Pick the first hitstruct that meets the specified condition
    //and copy it into the returned hitstruct.

    vector<HitStruct> LeftEntries;
    vector<HitStruct> LeftExits;
    vector<HitStruct> RightEntries;
    vector<HitStruct> RightExits;
    //ALLHITS SHOULD BE SORTED BY T VALUE
    vector<HitStruct> AllHits;
    
    bool hitLeft;
    bool hitRight;
    bool hit;
 
    double leftTMin = -DBL_MAX;
    //get all the intersections of the left shape
    do
    {
        HitStruct lhs;
        hit = LeftShape->TestIntersect(ray, leftTMin, tmax, lhs);
        if(hit)
        {
            if(lhs.normal.dot(ray.GetDirection()) < 0.0)
            {
                LeftEntries.push_back(lhs);
            }
            else
            {
                LeftExits.push_back(lhs);
            }
            leftTMin = lhs.tVal + FLT_EPSILON;
            AllHits.push_back(lhs);
            hitLeft = true;
        }
    }
    while(hit);

    double rightTMin = -DBL_MAX;
    //Get all the intersections of the right shape
    do
    {
        HitStruct rhs;
        hit = RightShape->TestIntersect(ray, rightTMin, tmax, rhs);
        if(hit)
        {
            if(rhs.normal.dot(ray.GetDirection()) < 0.0)
            {
                RightEntries.push_back(rhs);
            }
            else
            {
                RightExits.push_back(rhs);
            }
            rightTMin = rhs.tVal + FLT_EPSILON;
            AllHits.push_back(rhs);
            hitRight = true;
        }
    }
    while(hit);

    HitStructComparitor hc = HitStructComparitor();
    sort(AllHits.begin(), AllHits.end(), hc);
    
    if(hitLeft || hitRight){
    //ensures at least ONE shape was hit
        switch(Operation)
        {
            case UNION:
            {
                for(int h = 0; h<AllHits.size(); h++)
                {
                    if(isInside(AllHits[h].tVal + FLT_EPSILON, LeftEntries, LeftExits) ||
                       isInside(AllHits[h].tVal + FLT_EPSILON, RightEntries, RightExits))
                    {

                        if(AllHits[h].tVal > tmin && AllHits[h].tVal < tmax){
                        hitStruct.tVal = AllHits[h].tVal;
                        hitStruct.normal = AllHits[h].normal;
                        if(ray.GetDirection().dot(AllHits[h].normal) > 0.0) //the normal is wrong, flip it
                            hitStruct.normal *= -1.0;
                        return true;
                        }
                    }
                }
                return false;
            }
            break;

            case DIFFERENCE:
            {
                for(int h = 0; h<AllHits.size(); h++)
                {
                    if(isInside(AllHits[h].tVal + FLT_EPSILON, LeftEntries, LeftExits) &&
                       !isInside(AllHits[h].tVal + FLT_EPSILON, RightEntries, RightExits))
                    {
                        if(AllHits[h].tVal > tmin && AllHits[h].tVal < tmax) {
                            hitStruct.shader = this->shader;
                            hitStruct.tVal = AllHits[h].tVal;
                            hitStruct.normal = AllHits[h].normal;
                            if (ray.GetDirection().dot(AllHits[h].normal) > 0.0) //the normal is wrong, flip it
                                hitStruct.normal *= -1.0;
                            return true;
                        }
                    }
                }
                return false;
            }
            break;

            case INTERSECTION:
            {
                for(int h = 0; h<AllHits.size(); h++)
                {
                    if(isInside(AllHits[h].tVal + FLT_EPSILON, LeftEntries, LeftExits) &&
                       isInside(AllHits[h].tVal + FLT_EPSILON, RightEntries, RightExits))
                    {
                        if(AllHits[h].tVal > tmin && AllHits[h].tVal < tmax){
                            hitStruct.shader = this->shader;
                            hitStruct.tVal = AllHits[h].tVal;
                            hitStruct.normal = AllHits[h].normal;
                            if(ray.GetDirection().dot(AllHits[h].normal) > 0.0) //the normal is wrong, flip it
                                hitStruct.normal *= -1.0;
                            return true;
                        }
                    }
                }
                return false;
            }
            break;
        }
    }
    else
        return false; //missed both shapes
}

double Construct::GetSortValue(int sortBy)
{
    if(sortBy == X_VAL){
        return (GetBoundingBox()->GetXMax() + GetBoundingBox()->GetXMin())/2.0;
    }
    if(sortBy == Y_VAL){
        return (GetBoundingBox()->GetYMax() + GetBoundingBox()->GetYMin())/2.0;
    }
    if(sortBy == Z_VAL){
        return (GetBoundingBox()->GetZMax() + GetBoundingBox()->GetZMin())/2.0;
    }
}

void Construct::Finalize()
{
    this->boundingBox = new BoundingBox(*LeftShape->GetBoundingBox());
    boundingBox->Absorb(RightShape->GetBoundingBox());
}


static bool isInside(double t, vector<HitStruct>& entries, vector<HitStruct>& exits)
{
    for(int i = 0; i<entries.size(); i++)
    {
        if(i >= exits.size()) //unmatched entry
            continue;
        if(entries[i].tVal < t && exits[i].tVal > t)
        {
            return true;
        }
    }
    return false;
}